package com.sc.nft.dao;

import cn.hutool.core.util.BooleanUtil;
import com.sc.nft.annotation.PartitionField;
import com.sc.nft.annotation.PartitionPK;
import com.sc.nft.annotation.PartitionTable;
import com.sc.nft.entity.UserWalletDetail;
import com.sc.nft.helper.ElasticsearchHelper;
import com.sc.nft.helper.TableIdGenerator;
import com.sc.nft.helper.TablePartitionExecutor;
import com.sc.nft.helper.TablePartitionRouter;
import com.sc.nft.mapper.UserWalletDetailMapper;
import lombok.extern.slf4j.Slf4j;
import org.elasticsearch.action.search.SearchRequest;
import org.elasticsearch.search.aggregations.AggregationBuilders;
import org.elasticsearch.search.aggregations.Aggregations;
import org.elasticsearch.search.aggregations.metrics.ParsedSum;
import org.elasticsearch.search.builder.SearchSourceBuilder;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Repository;
import org.springframework.util.CollectionUtils;

import javax.annotation.Resource;
import java.io.IOException;
import java.math.BigDecimal;
import java.time.LocalDate;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Objects;
import java.util.stream.Collectors;

import static com.sc.nft.constant.TableNameConstants.USER_WALLET_DETAIL;
import static org.elasticsearch.index.query.QueryBuilders.*;

/**
 * 钱包明细表(sc_user_wallet_detail)数据DAO
 * <p>
 * MySQL表数据按 $userId 分片，同时使用ES异构，查询条件涉及 $userId 延用原查询逻辑，使用动态路由来控制数据源，其它查询改走ES查询
 *
 * @author kancy
 * @description 由 Mybatisplus Code Generator 创建
 * @since 2022-08-18 01:22:40
 */
@Slf4j
@Repository
public class UserWalletDetailDao {

    @Resource
    private UserWalletDetailMapper baseMapper;

    @Autowired
    private TableIdGenerator tableIdGenerator;

    @Autowired
    private ElasticsearchHelper elasticsearchHelper;

    private static final String TABLE = USER_WALLET_DETAIL;

    @Value("${app.metago.es_index.user_wallet_detail:meta-user_wallet_detail-alias}")
    private String indexAlias;

    public Boolean saveBatch(List<UserWalletDetail> userWalletDetails) {
//        return baseMapper.saveBatch(userWalletDetails);

        if (CollectionUtils.isEmpty(userWalletDetails)) {
            return true;
        }

        // 按 $userId 分组插入
        final TablePartitionRouter.Router router = TablePartitionRouter.ofUserWalletDetail();
        return userWalletDetails.stream()
                .filter(Objects::nonNull)
                .filter(item -> item.getUserId() != null)
                .collect(Collectors.groupingBy(item -> router.routeWithField(item.getUserId())))    // 按 $userId 分组
                .entrySet()
                .stream()
                .map(entry -> TablePartitionExecutor.exec(entry.getKey(), (partition) -> {
                    // 生成并填充ID列表
                    tableIdGenerator.fillAutoIncrementIds(partition, entry.getValue(), this::nextIds, UserWalletDetail::setId);
                    // 批量插入
                    return baseMapper.saveBatch(entry.getValue());
                }))
                .allMatch(BooleanUtil::isTrue);
    }

    private List<Long> nextIds(String partition, int count) {
        // 注：这里的 $item 必须使用分片索引，不能直接使用 $merchantsId，否则会造成主键冲突，原因是主键前缀是使用分片索引表示的，
        // 而自增主键表中如果使用 $merchantsId 作区分就会导致相同分区的主键出现冲突
        return tableIdGenerator.defaultNextIds(TABLE, partition, count);
    }

    private BigDecimal parseAmountSumValue(Aggregations aggregations) {
        return BigDecimal.valueOf(((ParsedSum) aggregations.get("sum_amount")).getValue());
    }

    public BigDecimal getSumPayBalance(Boolean isAdd) {
//        return baseMapper.getSumPayBalance(isAdd);

        /*
        <select id="getSumPayBalance" resultType="java.math.BigDecimal">
            SELECT
                sum(amount)
            FROM
                sc_user_wallet_detail
            WHERE
                is_add = #{isAdd}
                AND balance_type = 2
                AND is_delete = 0
                AND to_days( date_format( create_time, '%y-%m-%d' ) ) &lt; to_days( date_format( now( ), '%y-%m-%d' ) );
        </select>
         */

        SearchSourceBuilder source = new SearchSourceBuilder()
                .query(boolQuery()
                        .filter(termQuery("isAdd", isAdd))
                        .filter(termQuery("balanceType", 2))
                        .filter(termQuery("isDelete", false))
                        // 特殊条件：创建时间早于今天（< $Today 00:00:00.000）
                        .filter(rangeQuery("createTime").lt(elasticsearchHelper.ofDateTimeString(LocalDate.now()))
                        )
                )
                .size(0)
                .aggregation(AggregationBuilders.sum("sum_amount").field("amount"));

        try {
            SearchRequest request = new SearchRequest(indexAlias).source(source);
            return parseAmountSumValue(elasticsearchHelper.search(request).getAggregations());
        } catch (IOException e) {
            log.error("ES搜索出错 #getSumPayBalance()", e);
            return null;
        }

    }

    public BigDecimal getToDayBackstageAddByBalance() {
//        return baseMapper.getToDayBackstageAddByBalance();

        /*
        <select id="getToDayBackstageAddByBalance" resultType="java.math.BigDecimal">
            SELECT
                ifnull( sum(amount), 0 ) AS amount
            FROM
                sc_user_wallet_detail
            WHERE
                type = 6
                AND is_delete = 0
                AND is_add = 1
                AND balance_type = 1
                AND TO_DAYS( date_format( NOW(), '%y-%m-%d' ) ) - TO_DAYS( date_format( create_time, '%y-%m-%d' ) ) = 1
        </select>
         */

        SearchSourceBuilder source = new SearchSourceBuilder()
                .query(boolQuery()
                        .filter(termQuery("type", 6))
                        .filter(termQuery("isDelete", false))
                        .filter(termQuery("isAdd", true))
                        .filter(termQuery("balanceType", 1))
                        // 特殊条件：创建时间等于昨天[$Yesterday 00:00:00.000, $Today 00:00:00.000)
                        .filter(rangeQuery("createTime")
                                .gte(elasticsearchHelper.ofDateTimeString(LocalDate.now().minusDays(1L)))
                                .lt(elasticsearchHelper.ofDateTimeString(LocalDate.now()))
                        )
                )
                .size(0)
                .aggregation(AggregationBuilders.sum("sum_amount").field("amount"));

        try {
            SearchRequest request = new SearchRequest(indexAlias).source(source);
            return parseAmountSumValue(elasticsearchHelper.search(request).getAggregations());
        } catch (IOException e) {
            log.error("ES搜索出错 #getToDayBackstageAddByBalance()", e);
            return null;
        }
    }

    public BigDecimal getToDayBackstageAddByBalanceByDay(String day) {
//        return baseMapper.getToDayBackstageAddByBalanceByDay(day);

        /*
        <select id="getToDayBackstageAddByBalanceByDay" resultType="java.math.BigDecimal">
            SELECT
                ifnull( sum( amount ), 0 ) AS amount
            FROM
                sc_user_wallet_detail
            WHERE
                type = 6
                AND is_delete = 0
                AND is_add = 1
                AND balance_type = 1
                AND TO_DAYS( date_format( create_time, '%y-%m-%d' ) ) - TO_DAYS( date_format( #{day}, '%y-%m-%d' ) ) = -1;
        </select>
         */

        LocalDate date = LocalDate.parse(day);

        SearchSourceBuilder source = new SearchSourceBuilder()
                .query(boolQuery()
                        .filter(termQuery("type", 6))
                        .filter(termQuery("isDelete", false))
                        .filter(termQuery("isAdd", true))
                        .filter(termQuery("balanceType", 1))
                        // 特殊条件：创建时间等于给定时间前一天[$day-1 00:00:00.000, $day 00:00:00.000)
                        // $day 是一个格式为 yyyy-MM-dd 的字符串
                        .filter(rangeQuery("createTime")
                                .gte(elasticsearchHelper.ofDateTimeString(date.minusDays(1L)))
                                .lt(elasticsearchHelper.ofDateTimeString(date))
                        )
                )
                .size(0)
                .aggregation(AggregationBuilders.sum("sum_amount").field("amount"));

        try {
            SearchRequest request = new SearchRequest(indexAlias).source(source);
            return parseAmountSumValue(elasticsearchHelper.search(request).getAggregations());
        } catch (IOException e) {
            log.error("ES搜索出错 #getToDayBackstageAddByBalanceByDay()", e);
            return null;
        }
    }

    public BigDecimal getToDayIncomePayBalanceNum() {
//        return baseMapper.getToDayIncomePayBalanceNum();

        /*
        <select id="getToDayIncomePayBalanceNum" resultType="java.math.BigDecimal">
            SELECT
                ifnull( sum( amount ), 0 ) AS amount
            FROM
                sc_user_wallet_detail
            WHERE
                is_delete = 0
                AND balance_type = 2
                and is_add = 1
                AND TO_DAYS( date_format( NOW( ), '%y-%m-%d' ) ) - TO_DAYS( date_format( create_time, '%y-%m-%d' ) ) = 1
                AND type  in (10,9,6);
        </select>
         */

        SearchSourceBuilder source = new SearchSourceBuilder()
                .query(boolQuery()
                        .filter(termQuery("isDelete", false))
                        .filter(termQuery("balanceType", 2))
                        .filter(termQuery("isAdd", true))
                        .filter(termsQuery("type", Arrays.asList(10, 9, 6)))
                        // 特殊条件：创建时间等于给定时间前一天[$Yesterday-1 00:00:00.000, $Today 00:00:00.000)
                        .filter(rangeQuery("createTime")
                                .gte(elasticsearchHelper.ofDateTimeString(LocalDate.now().minusDays(1L)))
                                .lt(elasticsearchHelper.ofDateTimeString(LocalDate.now()))
                        )
                )
                .size(0)
                .aggregation(AggregationBuilders.sum("sum_amount").field("amount"));

        try {
            SearchRequest request = new SearchRequest(indexAlias).source(source);
            return parseAmountSumValue(elasticsearchHelper.search(request).getAggregations());
        } catch (IOException e) {
            log.error("ES搜索出错 #getToDayIncomePayBalanceNum()", e);
            return null;
        }
    }

    public BigDecimal getToDayIncomePayBalanceNumByDay(String day) {
//        return baseMapper.getToDayIncomePayBalanceNumByDay(day);

        /*
        <select id="getToDayIncomePayBalanceNumByDay" resultType="java.math.BigDecimal">
            SELECT
                ifnull( sum( amount ), 0 ) AS amount
            FROM
                sc_user_wallet_detail
            WHERE
                is_delete = 0
                AND balance_type = 2
                and is_add = 1
                AND TO_DAYS( date_format( create_time, '%y-%m-%d' ) ) - TO_DAYS( date_format( #{day}, '%y-%m-%d' ) ) = -1
                AND type  in (10,9,6);
        </select>
         */

        LocalDate date = LocalDate.parse(day);

        SearchSourceBuilder source = new SearchSourceBuilder()
                .query(boolQuery()
                        .filter(termQuery("isDelete", false))
                        .filter(termQuery("balanceType", 2))
                        .filter(termQuery("isAdd", true))
                        .filter(termsQuery("type", Arrays.asList(10, 9, 6)))
                        // 特殊条件：创建时间等于给定时间前一天[$day-1 00:00:00.000, $day 00:00:00.000)
                        // $day 是一个格式为 yyyy-MM-dd 的字符串
                        .filter(rangeQuery("createTime")
                                .gte(elasticsearchHelper.ofDateTimeString(date.minusDays(1L)))
                                .lt(elasticsearchHelper.ofDateTimeString(date))
                        )
                )
                .size(0)
                .aggregation(AggregationBuilders.sum("sum_amount").field("amount"));

        try {
            SearchRequest request = new SearchRequest(indexAlias).source(source);
            return parseAmountSumValue(elasticsearchHelper.search(request).getAggregations());
        } catch (IOException e) {
            log.error("ES搜索出错 #getToDayIncomePayBalanceNumByDay()", e);
            return null;
        }
    }

    /**
     * 杉德代付期间的历史提现金额 + 平台转钱包已到账金额(含杉德+汇付)
     *
     * @param userId
     * @return
     */
    @PartitionTable(TABLE)
    public BigDecimal historicalWithdrawal(@PartitionField Long userId) {
        return baseMapper.historicalWithdrawal(userId).subtract(baseMapper.historicalTransferFail(userId));
    }

//    /**
//     * 平台可用余额(包含红包)
//     *
//     * @param userId
//     * @return
//     */
//    @PartitionTable(TABLE)
//    public BigDecimal platformAvailableBalance(@PartitionField Long userId) {
//        return baseMapper.platformAvailableBalance(userId);
//    }

    /**
     * 充值金额
     *
     * @param userId
     * @return
     */
    @PartitionTable(TABLE)
    public BigDecimal rechargeAmount(@PartitionField Long userId) {
        return baseMapper.rechargeAmount(userId);
    }

    /**
     * 替代原充血模型中的 #insert() 方法
     */
    public void insert(UserWalletDetail model) {
        // 分区实现
        this.saveBatch(Collections.singletonList(model));
    }

    @PartitionTable(TABLE)
    public UserWalletDetail findById(@PartitionPK Long id) {
        return baseMapper.selectById(id);
    }

}